package com.zhj.helloframework.injection;

import com.zhj.helloframework.core.BeanContainer;
import com.zhj.helloframework.injection.annotation.Autowired;
import com.zhj.helloframework.util.ClassUtil;
import com.zhj.helloframework.util.ValidationUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.Set;

/**
 * @Author: zhj
 * @Description: 实现属性的依赖注入
 * @Date: 2021/10/7 16:50
 * @Version: 1.0
 */
@Slf4j
public class DependencyInjector {
    /**
     * Bean容器
     */
    private BeanContainer beanContainer;

    // 初始化DependencyInjector时，手动指定beanContainer的属性值
    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 执行IOC
     * 当该方法执行完毕后，所有被指定注解标记的对象都会被实例化，并且属性也会被赋值
     */
    public void doIoc() {
        // 对IOC容器中获取的Class对象进行判断，如果为空，直接返回
        if (ValidationUtil.isEmpty(beanContainer.getClasses())) {
            log.warn("empty classes in BeanContainer");
            return;
        }

        // 为所有被@Autowired注解标记的属性赋值
        for (Class<?> clazz : beanContainer.getClasses()) {// 遍历容器中的所有class的对象
            // 获取所有当前Class对象级别的属性（对象的字段），不包括其父级别的属性
            Field[] fields = clazz.getDeclaredFields();

            if (ValidationUtil.isEmpty(fields)) {
                continue;// 结束本次循环
            }

            for (Field field : fields) {
                // 找出被Autowired标记的成员变量
                if (field.isAnnotationPresent(Autowired.class)) {
                    // 获得该元素的指定类型的注释
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    // 获取@Autowired中指定的beanName，默认为 ""
                    String autowiredValue = autowired.value();

                    // 获取该field（某个字段）的类型(即AspectTarget类型)
                    Class<?> fieldClass = field.getType();
                    // 获取该类型在容器中对应的实例(也就是获取beanContainer中的AspectTarget实例)
                    Object fieldValue = getFieldInstance(fieldClass,autowiredValue);
                    if (fieldValue == null) {
                        throw new RuntimeException("unable to inject relevant type, target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue);
                    } else {
                        // 通过反射将对应的成员变量实例注入到成员变量所在类的实例里(即将AspectTarget实例注入到IocTest实例中)
                        Object targetBean = beanContainer.getBean(clazz);// (IocTest实例)
                        ClassUtil.setField(field,targetBean,fieldValue,true);
                    }
                }
            }
        }
    }

    /**
     * 根据Class在beanContainer里获取实例或者实现类
     * @param fieldClass     实例中属性的类型
     * @param autowiredValue 要注入的值的名称
     * @return
     */
    private Object getFieldInstance(Class<?> fieldClass,String autowiredValue) {
        // 看能否获取同等级的bean实例(fieldClass即AspectTarget)
        Object fieldValue = beanContainer.getBean(fieldClass);
        if (fieldValue != null) {
            return fieldValue;
        } else {
            Class<?> implementedClass = geImplementedClass(fieldClass, autowiredValue);
            if (implementedClass != null) {
                return beanContainer.getBean(implementedClass);
            } else {
                return null;
            }
        }
    }

    /**
     * 获取接口的实现类
     * @param fieldClass 实例中要注入的属性的类型
     * @param autowiredValue 要注入的值的名称
     * @return
     */
    private Class<?> geImplementedClass(Class<?> fieldClass,String autowiredValue) {
        Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
        if (!ValidationUtil.isEmpty(classSet)) {
            if (ValidationUtil.isEmpty(autowiredValue)) {
                if (classSet.size() ==  1) {
                    return classSet.iterator().next();
                } else {
                    throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + "please set @Autowired's value to pick one");
                }
            } else {
                for (Class<?> clazz : classSet) {
                    if (autowiredValue.equals(clazz.getSimpleName())) {
                        return clazz;
                    }
                }
            }
        }
        return null;
    }

}
